package com.magenta.maxoptra.yandex.json.parsers;

import com.magenta.maxoptra.gis.commons.Logger;
import com.magenta.maxoptra.gis.GeoException;
import com.magenta.maxoptra.gis.GeoAddress;
import com.magenta.maxoptra.gis.utils.DoForwardGeocodingProfiler;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
 * @author Zaur Muhametgaleev
 * @version 12/20/11 12:05 PM
 * @see "jira task MRS-4161: Add support of Yandex geo provider"
 * @since Maxoptra 3 (GT)
 */
public class ForwardGeocodingJSONParser {
    private Logger logger = new Logger(getClass());
    public static final String PATTERN_ONE = "^ул|^г|^д|^пр|^б|^ш|^пос|^пер|%3B|^пр-кт|^с|^обл|^кв";
    public static final String PATTERN_TWO = "^ул|^гор|^д|^прос|^бул|^шос|^пос|^пер|%3B|^прос|^сел|^обл|^микро";
    public static final String CLEAN_BLOCK = "\\d+(\\s|,|[а-г]|)+к+(\\D{0,6}|)\\d?";


    private static void extractCoordinates(GeoAddress result, JSONObject geoData) throws GeoException {
        Object point;
        try {
            point = geoData.get("Point");

            if (point instanceof JSONObject) {
                Object pos;
                pos = ((JSONObject) point).get("pos");
                String pointStr = (String) pos;
                String[] splittedPoint = pointStr.trim().split(" ");
                Double lon = Double.parseDouble(splittedPoint[0]);
                Double lat = Double.parseDouble(splittedPoint[1]);
                result.setLatitude(lat);
                result.setLongitude(lon);
            }
        } catch (JSONException e) {
            throw new GeoException("Geo coordinates are not presented");
        }
    }

    private void extractAddressDetails(GeoAddress result, Object addressDetails) throws GeoException {
        try {
            if (addressDetails instanceof JSONObject) {
                Object countryObject = ((JSONObject) addressDetails).get("Country");
                if (countryObject instanceof JSONObject) {
                    JSONObject country = (JSONObject) countryObject;
                    String countryName = (String) country.get("CountryName");
                    result.setCountry(countryName);
                    extractState(result, country);
                }
            }
        } catch (JSONException e) {
            throw new GeoException("Country not presented");
        }
    }

    private void extractState(GeoAddress result, JSONObject country) {
        try {
            Object administrativeAreaObject = country.get("AdministrativeArea");
            if (administrativeAreaObject != null) {
                if (administrativeAreaObject instanceof JSONObject) {
                    JSONObject administrativeArea = (JSONObject) administrativeAreaObject;
                    String state = (String) administrativeArea.get("AdministrativeAreaName");
                    result.setState(state);
                    extractSubadministrative(result, administrativeArea);
                }
            } else {
                extractCityStreetHouse(result, country);
            }
        } catch (JSONException e) {
            logger.warn("County is not presented");
        }
    }

    private void extractSubadministrative(GeoAddress result, JSONObject administrative) throws JSONException {
        try {
            Object subAdministrativeArea = administrative.get("SubAdministrativeArea");
            if (subAdministrativeArea instanceof JSONObject) {

                    JSONObject subArea = (JSONObject) subAdministrativeArea;
                    String county = (String) subArea.get("SubAdministrativeAreaName");
                    result.setCounty(county);
                    extractCityStreetHouse(result, subArea);
                }
//            }
        } catch (JSONException e) {
            logger.warn("City not presented");
        }
    }

    private void extractCityStreetHouse(GeoAddress result, JSONObject subArea) throws JSONException {
        try {

                Object localityObject =((JSONObject)subArea).get("Locality");
                if (localityObject instanceof JSONObject) {
                    JSONObject locality = (JSONObject) localityObject;
                    String city = (String) locality.get("LocalityName");
                    result.setCity(city);
                    extractStreet(result, locality);
                }
        } catch (JSONException e) {
            logger.warn("City not presented");
        }
    }

    private void extractStreet(GeoAddress result, JSONObject locality) throws JSONException {
        try {
            Object thoroughfareObject = locality.get("Thoroughfare");
            if (thoroughfareObject instanceof JSONObject) {
                JSONObject thoroghfare = (JSONObject) thoroughfareObject;
                String street = (String) thoroghfare.get("ThoroughfareName");
                result.setStreet(street);
                extractHouseNumber(result, thoroghfare);
            }
        } catch (JSONException e) {
            logger.warn("Street not presented");
        }
    }

    private void extractHouseNumber(GeoAddress result, JSONObject thoroghfare) {
        try {
            Object premiseObject = thoroghfare.get("Premise");
            if (premiseObject instanceof JSONObject) {
                JSONObject premise = (JSONObject) premiseObject;
                String house = (String) premise.get("PremiseNumber");
                result.setHouse(house);
            }
        } catch (JSONException e) {
            logger.warn("House number is not presented");
        }
    }

    public static String[] excludeDefinitions(String[] sources, String pattern) {
        Pattern regex = Pattern.compile(pattern);
        ArrayList<Integer> saveIndexes = new ArrayList();
        for (int i = 0; i < sources.length; i++) {
            String str = sources[i];
            Matcher matcher = regex.matcher(str);
            if (!matcher.find()) {
                saveIndexes.add(i);
            }
        }
        String result[] = new String[saveIndexes.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = sources[saveIndexes.get(i)];
        }
        return result;
    }

    public static String[] dropGarbage(String source) {
        StringTokenizer tokenizer = new StringTokenizer(source, ",;. ");
        String result[] = new String[tokenizer.countTokens()];
        for (int i = 0; i < result.length; i++) {
            result[i] = tokenizer.nextToken();
        }
        return result;
    }

    public List<GeoAddress> modiFiedParseJson(String source, String jsonAsString, boolean isSearchByResult, int cnt,
                                              DoForwardGeocodingProfiler profiler) throws GeoException {
        List<GeoAddress> resultList = new ArrayList<GeoAddress>();
        try {

            JSONObject o = new JSONObject(jsonAsString);
            Object response = o.get("response");
            if (response instanceof JSONObject) {
                Object geoObjectCollection = ((JSONObject) response).get("GeoObjectCollection");
                if (geoObjectCollection instanceof JSONObject) {
                    Object featureMember = ((JSONObject) geoObjectCollection).get("featureMember");
                    if (featureMember instanceof JSONArray) {
                        JSONArray features = (JSONArray) featureMember;
                        if (features.length() != 0) {
                            source = cleanConvertStr(source);
                            if (checkIfAlone(source)) {
                                source = attachLetter(source);
                            }
                            for (int i = 0; i < cnt && i < features.length(); i++) {
                                Object geoObject = features.get(i);
                                boolean validationStatus = true;
                                if (!((geoObject.toString().contains("ThoroughfareName") || geoObject.toString().
                                        contains("DependentLocality")) && geoObject.toString().contains("PremiseNumber")))
                                    validationStatus = false;
                                if (geoObject instanceof JSONObject) {
                                    Object geoObjectInstance = ((JSONObject) geoObject).get("GeoObject");
                                    if (geoObjectInstance instanceof JSONObject) {
                                        JSONObject geoData = (JSONObject) geoObjectInstance;
                                        Object metaDataProperty = geoData.get("metaDataProperty");
                                        if (metaDataProperty instanceof JSONObject) {
                                            Object geocoderMetaDataObject = ((JSONObject) metaDataProperty).
                                                    get("GeocoderMetaData");
                                            if (geocoderMetaDataObject instanceof JSONObject) {
                                                Object addressDetails = ((JSONObject) geocoderMetaDataObject).
                                                        get("AddressDetails");
                                                if (addressDetails instanceof JSONObject) {
                                                    if (isSearchByResult) {
                                                        JSONObject geocoderMetaData = (JSONObject) geocoderMetaDataObject;
                                                        String address = (String) geocoderMetaData.get("text");
                                                        String[] results = dropGarbage(address);
                                                        String[] ok = excludeDefinitions(results, PATTERN_TWO);
                                                        for (String str : ok) {
                                                            if (str.equals("Россия") || str.equals("Беларусь") ||
                                                                    str.equals("Украина")) {
                                                                continue;
                                                            }
                                                            if (!source.toLowerCase().contains(str.toLowerCase())) {
                                                                validationStatus = false;
                                                            }
                                                        }
                                                    } else {
                                                        Object geocoderMetaData = ((JSONObject) geocoderMetaDataObject).
                                                                get("AddressDetails");
                                                        String sources[] = dropGarbage(source);
                                                        String ok[] = excludeDefinitions(sources, PATTERN_ONE);
                                                        if (geocoderMetaData instanceof JSONObject) {
                                                            String addressText = geocoderMetaData.toString();
                                                            for (String str : ok) {
                                                                if (!addressText.toLowerCase().contains(str.toLowerCase())) {
                                                                    validationStatus = false;
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                                GeoAddress geoAddress = new GeoAddress();
                                                extractCoordinates(geoAddress, geoData);
                                                extractAddressDetails(geoAddress, addressDetails);
                                                geoAddress.setIsAccurate(validationStatus);
                                                profiler.logDuration(geoAddress);
                                                resultList.add(geoAddress);
                                            }
                                        }
                                    }
                                }

                            }
                        }
                    }
                }
            }
        } catch (JSONException e) {
            throw new GeoException("Address is not resolved");
        }
        return resultList;
    }

    public static boolean checkIfAlone(String str) {
        String pattern = "[а-яА-Я]";
        Pattern regexp = Pattern.compile(pattern);
        int len = str.length();
        String st = str.substring(len - 1, len);
        Matcher matcher = regexp.matcher(st);
        if (matcher.find()) {
            if ((str.charAt(len - 2) == ' ' || str.charAt(len - 2) == ',') && Character.isDigit(str.charAt(len - 3))) {
                return true;
            }
        }
        return false;
    }

    public static String attachLetter(String str) {
        return str.substring(0, str.length() - 2) + str.charAt(str.length() - 1);
    }

    public static String cleanConvertStr(String str) {
        Pattern regexp = Pattern.compile(CLEAN_BLOCK);
        if (!Character.isLetterOrDigit(str.charAt(str.length() - 1))) {
            str = str.substring(0, str.length() - 1);
        }
        Matcher matcher = regexp.matcher(str);
        String st = "";
        String replaceStr = "";
        String toBeReplaced = "";

        if (matcher.find()) {
            st = matcher.group();
            toBeReplaced = st;
            st = st.replaceAll("\\s|,", "");
            for (int i = 0; i < st.length() - 1; i++) {
                char c = st.charAt(i);
                if (c == 'к') {
                    String s = st.substring(i + 1, st.length());
                    s = s.replaceAll("\\D+", "");
                    replaceStr = st.substring(0, i + 1) + s;
                }
            }
            return str.replace(toBeReplaced, replaceStr);
        }
        return str;
    }
}